/*
 * ebctool.c - EBC Utils - EBC tool
 * Clunix, cezanne@clunix.com, 2005.3
 *
 * Copyright (c) 2001-2008 by Clunix Inc. All Rights Reserved.
 */
#include "config.h"

#include "ebctool.h"

ECMBOOL	verbose, continuation;

static ebc_t	*ec;

static char	*serverip;
static ecm_uint16	ebc_port = EBC_PORT_DEF;
static ECMBOOL	echo_server_mode, null_reply;
static char	*mdname;
static ecm_uint32	timeout = 5000;
static ebc_msgid_t	msgid;

void
ebctool_sr_print(sr_t *srp, char *prefix)
{
	sr_fancy_print(stdout, srp, SR_RECUR, prefix);
}

void
ebctool_usage(void)
{
	fprintf(stderr,
"Usage: ebctool [-e <module name>] [ <options> ]\n"
" -e : run as EBC echo server.\n"
" Options:\n"
"   -s <serverip>\n"
"   -p <ebc port>\n"
"   -n <ebc module name>\n"
"   -t <EBC timeout in msec>\n"
"   -v : verbose mode\n"
"   -N : null reply\n"
#ifdef ECM_DEBUG
"   -d <dbgset> : EBC debug set\n"
"   -m <msgid> : set msgid\n"
"   -M <IDMAP> : IDMAP \n"
"   -D <dbgset> : ebctool debugset(Flex, Bison)\n"
"   -X <nd name>: get ndid\n"
"   -Y <md name>: get mdid\n"
"   -Z <pattern name>: get ptid\n"
#endif
		);
}

void
cmd_connect(const char *serverip, ecm_uint16 port, const char *mdname)
{
	ebc_t	*ec_new;

	if (port == 0)
		port = ebc_port;
	if (ebc_connect(serverip, port, mdname, timeout, (ecm_ebc_t *)&ec_new) == EBC_OK) {
		ebc_close(ec);
		ec = ec_new;
	}
	else
		fprintf(stderr, "cannot connect\n");
}

void
cmd_peercon(char *peeraddr, ecm_uint32 timeout)
{
	ebc_addr_t	addr;
	ecm_ebc_t	peer_ebc;

	if (!ebc_get_addr(ec, peeraddr, &addr)) {
		fprintf(stderr, "%s: invalid address\n", peeraddr);
		ecm_free(peeraddr);
		return;
	}
	ecm_free(peeraddr);
	if (ebc_connect_peer(ec, &addr, timeout, &peer_ebc) != EBC_OK) {
		fprintf(stderr, "cannot connect to peer\n");
		return;
	}
	ec = peer_ebc;
}

void
cmd_call(char *target, ecm_uint32 timeout, char *asr)
{
	sr_t	*sr_arg;
	ebc_err_t	err;

	sr_arg = sr_asr_scan(asr);
	if (timeout > 0)
		err = ebc_call_timeout(ec, target, &sr_arg, timeout);
	else
		err = ebc_call(ec, target, &sr_arg);
	if (err != EBC_OK) {
		fprintf(stderr, "can't EBC call to [%s]: %d\n", target, err);
		sr_fancy_print(stdout, sr_arg, SR_RECUR, NULL);
	}
	else
		sr_fancy_print(stdout, sr_arg, SR_RECUR, NULL);
	sr_free(sr_arg);
	ecm_free(target);
	ecm_free(asr);
}

void
cmd_call_oneway(char *target, char *asr)
{
	sr_t	*sr_arg;
	ebc_err_t	err;

	sr_arg = sr_asr_scan(asr);
	err = ebc_call_oneway(ec, target, sr_arg);
	if (err != EBC_OK)
		fprintf(stderr, "can't EBC call oneway to [%s]: %d\n", target, err);
	sr_free(sr_arg);
	ecm_free(target);
	ecm_free(asr);
}

static void
print_perf(ecm_perf_t perf)
{
	ecm_show_perf(perf, ecm_stdout, "Bandwidth: %b, User: %u, Sys: %s\n");
}

typedef ECMBOOL (*bulk_job_t)(ebc_addr_t *paddr, sr_t *sr_oct);

static void
do_bulk_job(char *target, ecm_uint32 size, ecm_uint32 count, bulk_job_t job)
{
	ebc_addr_t	addr;
	sr_t	*sr_oct;
	ecm_ptr_t	data;
	ecm_perf_t	perf;
	ecm_uint32	i;

	sr_oct = sr_create(ST_OCT);
	data = ecm_malloc(size);
	sr_set_val(sr_oct, sr_val_oct(size, data));

	ebc_get_addr(ec, target, &addr);

	perf = ecm_create_perf(0);
	ecm_start_perf(perf);

	for (i = 0; i < count; i++) {
		if (!job(&addr, sr_oct))
			break;
	}
	ecm_add_perf_data(perf, 0, size * count);
	ecm_stop_perf(perf);
	if (i == count)
		print_perf(perf);

	ecm_destroy_perf(perf);
	sr_free(sr_oct);
	ecm_free(target);
}

static ECMBOOL
bulk_call(ebc_addr_t *paddr, sr_t *sr_oct)
{
	sr_t	*sr_res;

	if (ebc_dcall_nofree(ec, paddr, sr_oct, &sr_res) != EBC_OK) {
		fprintf(stderr, "can't EBC call\n");
		return ECM_FALSE;
	}
	sr_free(sr_res);
	return ECM_TRUE;
}

void
cmd_bcall(char *target, ecm_uint32 size, ecm_uint32 count)
{
	do_bulk_job(target, size, count, bulk_call);
}

void
cmd_send(char *target, char *asr)
{
	sr_t	*sr_arg;
	ebc_addr_t	daddr;
	ebc_err_t	err;

	sr_arg = sr_asr_scan(asr);
	if (ebc_get_addr(ec, target, &daddr)) {
		err = ebc_send(ec, &daddr, sr_arg);
		if (err != EBC_OK)
			fprintf(stderr, "can't send to [%s]: %d\n", target, err);
	}
	else {
		fprintf(stderr, "[%s]: invalid address\n", target);
	}
	sr_free(sr_arg);
	ecm_free(target);
	ecm_free(asr);
}

void
cmd_recv(char *pattern)
{
	sr_t	*sr;

	if (ebc_recv(ec, pattern, NULL, &sr) != EBC_OK)
		fprintf(stderr, "can't recv with pattern [%s]\n", pattern);
	else {
		sr_fancy_print(ecm_stderr, sr, SR_RECUR, NULL);
		sr_free(sr);
	}
	ecm_free(pattern);
}

static ECMBOOL
bulk_send(ebc_addr_t *paddr, sr_t *sr_oct)
{
	if (ebc_send(ec, paddr, sr_oct) != EBC_OK) {
		fprintf(stderr, "can't send\n");
		return ECM_FALSE;
	}
	return ECM_TRUE;
}

void
cmd_bsend(char *target, ecm_uint32 size, ecm_uint32 count)
{
	do_bulk_job(target, size, count, bulk_send);
}

void
cmd_brecv(char *pattern, ecm_uint32 count)
{
	sr_t	*sr;
	ecm_perf_t	perf;
	ecm_uint32	i;
	ecm_uint32	size = 0;

	perf = ecm_create_perf(0);
	for (i = 0; i < count; i++) {
		if (ebc_recv(ec, pattern, NULL, &sr) != EBC_OK) {
			fprintf(stderr, "can't recv with pattern [%s]. %ubulk(s) received.\n", pattern, i);
			break;
		}
		if (i == 1)
			ecm_start_perf(perf);
		if (i > 0)
			size += sr_get_oct_size(sr);
		sr_free(sr);
	}
	if (count > 2 && i == count) {
		ecm_add_perf_data(perf, 0, size);
		ecm_stop_perf(perf);
		print_perf(perf);
	}
	ecm_destroy_perf(perf);
	ecm_free(pattern);
}

static ECMBOOL
bulk_peersend(ebc_addr_t *paddr, sr_t *sr_oct)
{
	if (ebc_peer_send(ec, sr_get_oct_data(sr_oct), sr_get_oct_size(sr_oct)) != EBC_OK) {
		fprintf(stderr, "can't peer send\n");
		return ECM_FALSE;
	}
	return ECM_TRUE;
}

void
cmd_peerbsend(ecm_uint32 size, ecm_uint32 count)
{
	do_bulk_job(NULL, size, count, bulk_peersend);
}

void
cmd_peerbrecv(ecm_uint32 count)
{
	ecm_perf_t	perf;
	ecm_uint8	*data;
	ecm_uint32	i;
	ecm_uint32	size = 0, datasize;

	perf = ecm_create_perf(0);
	for (i = 0; i < count; i++) {
		if (ebc_peer_arecv(ec, &data, &datasize, NULL) != EBC_OK) {
			fprintf(stderr, "can't recv from peer. %ubulk(s) received.\n", i);
			break;
		}
		if (i == 1)
			ecm_start_perf(perf);
		if (i > 0)
			size += datasize;
		ecm_free(data);
	}
	if (count > 2 && i == count) {
		ecm_add_perf_data(perf, 0, size);
		ecm_stop_perf(perf);
		print_perf(perf);
	}
	ecm_destroy_perf(perf);
}

static sr_t *
echo_cb_default(ecm_ebc_t ecm_ebc, ebc_addr_t *saddr, ebc_addr_t *daddr, sr_t *sr, ecm_ptr_t ctx)
{
	if (sr_get_type(sr) != ST_OCT)
		sr_fancy_print(stdout, sr, SR_RECUR, NULL);
	if (ctx)
		return NULL;
	if (null_reply)
		return NULL;
	return sr_dup(sr);
}

static void
mon_cb(ecm_ebc_t ecm_ebc, ebc_addr_t *addr, ECMBOOL is_alive, ecm_ptr_t ctx)
{
#ifdef ECM_DEBUG
	printf("monitor callback: %s from %s\n",
	       is_alive ? "alive": "dead", ebc_dbg_addr(addr));
#else
	printf("monitor callback: %s\n", is_alive ? "alive": "dead");
#endif
}

void
cmd_echo_server(void)
{
	ebc_err_t	err;

	ebc_ecb_add(ec, NULL, echo_cb_default, NULL);
	if ((err = ebc_start_app_loop(ec, 0)) != EBC_OK)
		fprintf(stderr, "error: %s\n", ebc_errmsg(err));
	else {
		printf("** Press enter key stop echo server..\n");
		getchar();
		ebc_stop_app_loop(ec);
	}
	ebc_ecb_del(ec, NULL);
}

void
cmd_moncb_add(const char *target)
{
	ebc_addr_t	addr;
	ECMBOOL		alive;

	if (!ebc_get_addr(ec, target, &addr)) {
		fprintf(stderr, "%s: invalid address\n", target);
		return;
	}
	if (ebc_add_moncb(ec, &addr, mon_cb, NULL, &alive) != EBC_OK) {
		fprintf(stderr, "%s: cannot add monitor callback\n", target);
		return;
	}
	printf("%s: %s\n", target, alive ? "alive": "dead");
}

void
cmd_moncb_del(const char *target)
{
	ebc_addr_t	addr;

	if (!ebc_get_addr(ec, target, &addr)) {
		fprintf(stderr, "%s: invalid address\n", target);
		return;
	}
	if (ebc_del_moncb(ec, &addr) != EBC_OK)
		fprintf(stderr, "%s: cannot remove monitor callback\n", target);
}

void
cmd_route_print(void)
{
	sr_t	*sr_route_info;

	if (ebc_route_get(ec, &sr_route_info) != EBC_OK) {
		fprintf(stderr, "cannot get route information!\n");
		return;
	}
	sr_fancy_print(stdout, sr_route_info, SR_RECUR, 0);
}

void
cmd_route_add(const char *serverip)
{
	if (ebc_route_add(ec, serverip) != EBC_OK)
		fprintf(stderr, "route add failed!\n");
}

void
cmd_route_del(const char *serverip)
{
	if (ebc_route_del(ec, serverip) != EBC_OK)
		fprintf(stderr, "route del failed!\n");
}

#ifdef ECM_DEBUG

static void
do_pch_count(void)
{
	sr_t	*sr_arg = NULL;

	if (ebc_call_ebcd(ec, "pch_count", &sr_arg) != EBC_OK) {
		fprintf(stderr, "cannot get pch count\n");
		return;
	}
	printf("Accepted PCH count: %d\n", sr_getc_int(sr_arg, "n_accepted"));
	printf("Extern PCH count: %d\n", sr_getc_int(sr_arg, "n_epchs"));
	printf("Local PCH count: %d\n", sr_getc_int(sr_arg, "n_lpchs"));
	sr_free(sr_arg);
}

static void
do_pch_info(const char *addr)
{
	sr_t	*sr_arg;

	sr_arg = sr_create_str(ST_STR, addr);
	if (ebc_call_ebcd(ec, "pch_info", &sr_arg) != EBC_OK) {
		fprintf(stderr, "cannot get pch count\n");
		return;
	}

	if (sr_arg == NULL) {
		printf("%s: No such PCH\n", addr);
	}
	else {
		sr_t	*sr_res = sr_arg;

		printf("sending packet buffers: %u\n", sr_geti_int(sr_res, 0));
	}
	sr_free(sr_arg);
}

void
cmd_pch_info(const char *addr)
{
	if (ecm_empty_str(addr))
		do_pch_count();
	else
		do_pch_info(addr);
}

void
cmd_pch_list(void)
{
	sr_t	*sr_arg = NULL, *sr_pch_info;

	if (ebc_call_ebcd(ec, "pch_list", &sr_arg) != EBC_OK) {
		fprintf(stderr, "cannot get pch count\n");
		return;
	}
	
	SR_FOREACH_CHILD(sr_arg, NULL, sr_pch_info) {
		printf("%s\n", sr_get_name(sr_pch_info));
	}
	sr_free(sr_arg);
}

void
cmd_pktb(void)
{
	sr_t	*sr_arg;

	sr_arg = NULL;
	if (ebc_call_ebcd(ec, "pktb", &sr_arg) != EBC_OK) {
		fprintf(stderr, "cannot packet buffer information\n");
		return;
	}
	
	printf("total packet buffers: %u\n", sr_geti_int(sr_arg, 0));
	printf("sending packet buffers: %u\n", sr_geti_int(sr_arg, 1));

	sr_free(sr_arg);
}

static void
print_lcallinfo(const char *name, sr_t *sr_lcallinfos)
{
	sr_t	*sr_lcalli;

	if (sr_lcallinfos == NULL)
		return;
	printf("%s: %d\n", name, sr_getc_int(sr_lcallinfos, "count"));
	SR_FOREACH_CHILD(sr_lcallinfos, "callinfos", sr_lcalli) {
		printf(" +- %s: %d/%d\n",
		       sr_getc_str(sr_lcalli, "addr"),
		       sr_getc_int(sr_lcalli, "pktcur"),
		       sr_getc_int(sr_lcalli, "pktcnt"));
	}
	printf("\n");	
}

static void
print_ecallinfo(const char *name, sr_t *sr_ecallinfos)
{
	sr_t	*sr_ecalli;

	if (sr_ecallinfos == NULL)
		return;
	printf("%s: %d\n", name, sr_getc_int(sr_ecallinfos, "count"));
	SR_FOREACH_CHILD(sr_ecallinfos, "callinfos", sr_ecalli) {
		printf(" +- %s: %d/%d\n",
		       sr_getc_str(sr_ecalli, "call"),
		       sr_getc_int(sr_ecalli, "pktcur"),
		       sr_getc_int(sr_ecalli, "pktcnt"));
	}
	printf("\n");	
}

static void
print_rcallinfo(const char *name, sr_t *sr_rcallinfos)
{
	sr_t	*sr_rcalli;

	if (sr_rcallinfos == NULL)
		return;
	printf("%s: %d\n", name, sr_getc_int(sr_rcallinfos, "count"));
	SR_FOREACH_CHILD(sr_rcallinfos, "callinfos", sr_rcalli) {
		printf(" +- %s: %d/%d\n",
		       sr_getc_str(sr_rcalli, "addr"),
		       sr_getc_int(sr_rcalli, "pktcur"),
		       sr_getc_int(sr_rcalli, "pktcnt"));
	}
	printf("\n");	
}

static void
print_laudit(sr_t *sr_laudit)
{
	print_lcallinfo("local callers", sr_getc(sr_laudit, "lcallers"));
	print_lcallinfo("local callees", sr_getc(sr_laudit, "lcallees"));
	print_rcallinfo("remote callers", sr_getc(sr_laudit, "rcallers"));
	print_rcallinfo("remote callees", sr_getc(sr_laudit, "rcallees"));
}

static void
print_eaudit(sr_t *sr_eaudit)
{
	print_ecallinfo("ext callers", sr_getc(sr_eaudit, "ecallers"));
	print_ecallinfo("ext callees", sr_getc(sr_eaudit, "ecallees"));
}

void
cmd_call_info(void)
{
	sr_t	*sr_arg = NULL;
	sr_t	*sr_audit;

	if (ebc_call_ebcd(ec, "call_info", &sr_arg) != EBC_OK) {
		fprintf(stderr, "cannot get call info\n");
		return;
	}
	SR_FOREACH_CHILD(sr_arg, NULL, sr_audit) {
		printf("%s:\n", sr_get_name(sr_audit));
		if (sr_getc_bool(sr_audit, "local"))
			print_laudit(sr_audit);
		else
			print_eaudit(sr_audit);
	}

	sr_free(sr_arg);
}

void
cmd_monitor_info(void)
{
	sr_t	*sr_arg = NULL;

	if (ebc_call_ebcd(ec, "monitor_info", &sr_arg) != EBC_OK) {
		fprintf(stderr, "cannot get monitor info\n");
		return;
	}
	printf("orphan local monitors: %d\n", sr_getc_int(sr_arg, "orphan_lmonitors"));
	printf("orphan external monitors: %d\n", sr_getc_int(sr_arg, "orphan_emonitors"));
	printf("orphan external targets: %d\n", sr_getc_int(sr_arg, "orphan_etargets"));
	sr_free(sr_arg);
}

void
cmd_mtrace_info(void)
{
	sr_t	*sr_arg = NULL;

	if (ebc_call_ebcd(ec, "mtrace_info", &sr_arg) != EBC_OK) {
		fprintf(stderr, "cannot get mtrace info\n");
		return;
	}
	printf("memory tracing %s\n",
	       sr_geti_bool(sr_arg, 0) ? "on": "off");
	printf("memory used: %u, %u(per thread)\n",
	       sr_geti_int(sr_arg, 1),
	       sr_geti_int(sr_arg, 2));
	sr_free(sr_arg);
}

void
cmd_mtrace_on(ecm_uint32 level, ECMBOOL self_only)
{
	sr_t	*sr_arg;

	sr_arg = sr_create_dir();
	sr_append(sr_arg, sr_create_int(ST_INT, level));
	sr_append(sr_arg, sr_create_bool(ST_BOOL, self_only));
	if (ebc_call_ebcd(ec, "mtrace_on", &sr_arg) != EBC_OK) {
		fprintf(stderr, "mtrace on failed\n");
		return;
	}
	printf("memory tracing on\n");
}

void
cmd_mtrace_off(void)
{
	sr_t	*sr_arg = NULL;

	if (ebc_call_ebcd(ec, "mtrace_off", &sr_arg) != EBC_OK) {
		fprintf(stderr, "mtrace off failed\n");
		return;
	}
	printf("memory tracing off\n");
}

void
cmd_mtrace_report(void)
{
	sr_t	*sr_traces, *sr_trace, *sr_caller;

	sr_traces = NULL;
	if (ebc_call_ebcd(ec, "mtrace_report", &sr_traces) != EBC_OK) {
		fprintf(stderr, "mtrace report failed\n");
		return;
	}
	SR_FOREACH_CHILD(sr_traces, NULL, sr_trace) {
		printf("%s\n", sr_geti_str(sr_trace, 0));
		SR_FOREACH_CHILD(sr_geti(sr_trace, 1), NULL, sr_caller)
			printf("  %s\n", sr_get_str(sr_caller));
		printf("\n");
	}
}

void
cmd_mtrace_clear(void)
{
	sr_t	*sr_arg = NULL;

	if (ebc_call_ebcd(ec, "mtrace_clear", &sr_arg) != EBC_OK) {
		fprintf(stderr, "mtrace on failed\n");
		return;
	}
	printf("memory tracing cleared\n");
}

#endif

static void
parse_args(int argc, char *argv[])
{
	int	errflag = 0;
	int	c;

#ifdef ECM_DEBUG
	ct_flex_debug = 0;
#endif

	while ((c = ecm_getopt(argc, argv, "s:en:Nhp:t:vd:D:m:M:X:Y:Z")) != -1) {
		switch(c) {
		case 's':
			serverip = ecm_strdup(ecm_optarg);
			break;
		case 'e':
			echo_server_mode = ECM_TRUE;
			break;
		case 'n':
			mdname = ecm_strdup(ecm_optarg);
			break;
		case 'N':
			null_reply = ECM_TRUE;
			break;
		case 'h':
			ebctool_usage();
			exit(0);
			break;
		case 'p':
			if (ecm_sscanf(ecm_optarg, "%hu", &ebc_port) != 1)
				errflag++;
			break;
		case 't':
			if (ecm_sscanf(ecm_optarg, "%u", &timeout) != 1)
				errflag++;
			break;
		case 'v':
			verbose = ECM_TRUE;
			break;
#ifdef ECM_DEBUG
		case 'm':
			if (ecm_sscanf(ecm_optarg, "%hu", &msgid) != 1)
				errflag++;
			break;
		case 'M':
			ebc_dbg_setup_idmap(ecm_optarg);
			break;
		case 'd':
			if (!ebc_set_debugset_str(ecm_optarg))
				errflag++;
			break;
		case 'D':
			if (ecm_strchr(ecm_optarg, 'b'))
				ctdebug = 1;
			if (ecm_strchr(ecm_optarg, 'f'))
				ct_flex_debug = 1;
			break;
		case 'X': {
			ebc_ndid_t	ndid;

			ebc_get_ndid(ecm_optarg, &ndid);
			ecm_fprintf(ecm_stdout, "node id: %x(%s)\n", ndid, ecm_optarg);
			exit(0);
		}
		case 'Y': {
			ebc_ndid_t	mdid;

			ebc_get_mdid(ecm_optarg, &mdid);
			ecm_fprintf(ecm_stdout, "module id: %x(%s)\n", mdid, ecm_optarg);
			exit(0);
		}
		case 'Z': {
			ebc_ptid_t	ptid;

			ebc_get_ptid(ecm_optarg, &ptid);
			ecm_fprintf(ecm_stdout, "pattern id: %x(%s)\n", ptid, ecm_optarg);
			exit(0);
		}
#endif
		default: 
			errflag++;
		}
	}

	if (errflag) {
		ebctool_usage();
		exit(1);
	}
}

static char *rl_line = (char *)NULL;
static char *rl_start = (char *)NULL;
static int   rl_len = 0;

void
ebctool_readline(char *buf, int *result, int max)
{
	if (rl_len == 0) {
		char	*prompt;

		if (rl_start)
			ecm_free(rl_start);

		ecm_asprintf(&prompt, "%s:%s> ",
			     ecm_safe_str(ec->clname),
			     ecm_safe_str(ec->ndname));
		rl_start = ecm_readline(prompt);
		ecm_free(prompt);

		if (rl_start == NULL) {
			/* end of file */
			*result = 0;
			rl_len = 0;
			return;
		}
		rl_line = rl_start;
		rl_len = ecm_strlen(rl_line) + 1;
		rl_line[rl_len-1] = '\n';
		fflush(stdout);
	}

	if (rl_len <= max) {
		strncpy(buf, rl_line, rl_len);
		*result = rl_len;
		rl_len = 0;
	}
	else {
		strncpy (buf, rl_line, max);
		*result = max;
		rl_line += max;
		rl_len -= max;
	}
}

void
cterror(char *msg)
{
}

int
ctwrap(void)
{
	return 1;
}

int
main(int argc, char *argv[])
{
	ecm_initialize();
	ecm_set_output_file(stderr);

	/* set default ebc_port */
	ecm_sscanf(ecm_getenv(ECM_EBC_PORT_ENV), "%hu", &ebc_port);

	parse_args(argc, argv);

	/* anonymous ebc connect */
	if (ebc_connect(serverip, ebc_port, mdname, timeout, (ecm_ebc_t *)&ec) != EBC_OK) {
		fprintf(stderr, "ebc_connect to %hu error\n", ebc_port);
		exit(1);
	}
	if (msgid != 0)
		ec->mch->msgid = msgid;

	if (echo_server_mode) {
		cmd_echo_server();
		ebc_close(ec);
		return 0;
	}

	ctparse();

	ebc_close(ec);
	return 0;
}
